package com.ksyun.camp.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ksyun.camp.client.FeinClient;
import com.ksyun.camp.constant.AuthConstant;
import com.ksyun.camp.modle.dto.PayDto;
import com.ksyun.camp.modle.dto.TransferDto;
import com.ksyun.camp.modle.vo.AccountVo;
import com.ksyun.camp.modle.vo.AuthResponse;
import com.ksyun.camp.modle.vo.PayVo;
import com.ksyun.camp.modle.vo.Result;
import com.ksyun.camp.service.OnePassService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
@Service
public class OnePassServiceImpl implements OnePassService {

    @Autowired
    private FeinClient feinClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    @Qualifier("authThreadPool")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;



    /**
     * 用户间转账
     * @param transferDto 请求入参
     * @param requestId 请求ID
     * @return 全局响应对象
     */
    @Override
    public Result userTrade(TransferDto transferDto, String requestId) {
        // 获取 Redisson 锁对象
        RLock lock = redissonClient.getLock("userTradeLock");
        boolean isLocked = false;
        try {
            // 加载 lua 脚本
            String script = AuthConstant.LUA_TRANSFER_MONEY;


            ArrayList<String> keys = new ArrayList<>();
            keys.add(String.valueOf(transferDto.getSourceUid()));
            keys.add(String.valueOf(transferDto.getTargetUid()));

            DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>(script, Boolean.class);

            // 执行 lua 脚本
            // 尝试获取锁，最大等待时间为10秒，锁的有效时间为30秒
            isLocked= lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (!isLocked) {
                return Result.build(null, AuthResponse.LOCK_ACQUISITION_FAILED, requestId);
            }
            Boolean tradeSuccess = redisTemplate.execute(redisScript, keys, transferDto.getAmount().setScale(2, RoundingMode.HALF_UP).toString());
            if(Boolean.FALSE.equals(tradeSuccess)){
                return Result.build(null, AuthResponse.INSUFFICIENT_BALANCE, requestId);
            }

        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return Result.build(e.getMessage(), AuthResponse.SYSTEM_ERROR, requestId);
        } finally {
            // 释放锁
            if(isLocked){
                lock.unlock();
            }
        }

        return Result.build(null, AuthResponse.SUCCESS, requestId);
    }

    /**
     * 查询用户金额
     * @param uids 用户uid列表
     * @return
     */
    @Override
    public Result<List<AccountVo>> getUserAmount(List<Long> uids, String requestId) {
        // 将uid转为字符串
        List<String> hashKeys = uids.stream().map(String::valueOf).toList();

        // 获取所有uid对应的金额
        List<Object> amounts = redisTemplate.opsForHash().multiGet("user", new ArrayList<>(hashKeys));

        // 将结果转换为AccountVo对象列表
        List<AccountVo> accountVoList = uids.stream()
                .map(uid -> {
                    // 获取金额字符串
                    String amountStr = (String) amounts.get(hashKeys.indexOf(String.valueOf(uid)));
                    // 将金额字符串转换为BigDecimal
                    BigDecimal amount = (amountStr != null) ? new BigDecimal(amountStr) : BigDecimal.ZERO;
                    // 创建AccountVo对象
                    return AccountVo.builder()
                            .uid(uid)
                            .amount(amount)
                            .build();
                })
                .collect(Collectors.toList());

        // 返回结果
        return Result.build(accountVoList, AuthResponse.SUCCESS, requestId);
    }



    /**
     * 批量充值
     * @param payDto 充值入参
     * @param requestId 请求头中的请求ID
     * @return 全局响应对象
     */
    @Override
    public Result batchPay(PayDto payDto, String requestId){

        String taskKey = "batchPay:" + payDto.getBatchPayId();
        // 查询redis是否已经存在该任务
        String taskVal = redisTemplate.opsForValue().get(taskKey);
        if(taskVal != null){
            log.error("重复的任务请求，任务id：{}", payDto.getBatchPayId());
            return Result.build(null, AuthResponse.REPEAT_TASK, requestId);
        }
        // 获取要充值的账号列表
        List<Long> uids = payDto.getUids();

        try {
            // 向redis中添加一个计数的key，统计任务完成数
            redisTemplate.opsForValue().set(taskKey, String.valueOf(uids.size()));
            // 创建异步任务
            for (Long uid : uids) {
                CompletableFuture.runAsync(() -> payFromAccount(uid, payDto.getBatchPayId()), threadPoolTaskExecutor)
                        .exceptionally(e -> {
                            log.error(e.getMessage(), e);
                            throw new RuntimeException(e);
                        });
            }

//            // 等待所有异步任务完成
//            CompletableFuture<Void> allOf = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
//            allOf.join();  // 阻塞，直到所有任务完成
//            if(allOf.isDone()){
//                log.info("批处理完成");
//            }
        }catch (Exception e){
            return Result.build(e.getMessage(), AuthResponse.SYSTEM_ERROR, requestId);
        }
        return Result.build(null, AuthResponse.SUCCESS, requestId);
    }

    /**
     * 将金额存入redis
     * @param uid 账户uid
     * @param amount 存入的数量
     */
    private void payToRedis(Long uid , String amount){

        try{
//            String freeMoney = redisTemplate.opsForValue().get("user:" + uid);
//            if(freeMoney != null){
//                log.info("当前账户：{}，余额：{}", uid, freeMoney);
//            }
            // 加载lua脚本
            String luaScript = AuthConstant.LUA_ADD_MONEY;
            // 执行lua脚本
            ArrayList<String> keys = new ArrayList<>();
            keys.add(String.valueOf(uid));
            DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(luaScript, Boolean.class);
            redisTemplate.execute(script, keys, amount);
        }catch (Exception e){
            log.error(e.getMessage());
        }
    }

    /**
     * 从资金系统中获取账户金额发起充值
     * @param uid 账户uid
     * @param batchPayId 批处理充值任务id，主要在redis中用来标识任务的唯一性和任务量完成数
     */
    private void payFromAccount(Long uid, String batchPayId){

        // 创建CompletableFuture列表
        List<CompletableFuture<Void>> futures = IntStream.range(0, 100)
                .mapToObj(i -> CompletableFuture.runAsync(() -> do10000Pay(uid, batchPayId), threadPoolTaskExecutor))
                .toList();

        // 等待所有任务完成
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();

        // 少于资金账户少于10000
        doLess10000Pay(uid, batchPayId);

    }

    private void do10000Pay(Long uid, String batchPayId){
        PayVo payVo = PayVo.builder()
                .uid(uid)
                .amount(BigDecimal.valueOf(10000L))
                .build();
        while (true){
            try{
                String transactionId = UUID.randomUUID().toString();
                payVo.setTransactionId(transactionId);
                String requestID = UUID.randomUUID().toString();
                ResponseEntity<String> response = feinClient.getMoneyFromUsersAccount(
                        AuthConstant.STUDENT_ID,
                        requestID,
                        payVo
                );
//                log.info("线程：{}请求成功", Thread.currentThread().getName());
                JSONObject body = JSON.parseObject(response.getBody());
                if (body != null) {
                    Integer code = (Integer) body.get("code");
                    if(code != null && code == 501){
                        return;
                    }else if(code != null && code == 404){
//                        log.error("资金账户：{}不存在", payVo.getUid());
                        return;
                    }
//                    stringRedisTemplate.opsForHash().increment("user", payVo.getUid().toString(), addAmount);
                    payToRedis(uid, String.valueOf(payVo.getAmount()));
//                    log.info("充值{}成功",amount);
                }else {
                    log.error("未知错误");
                }
            }catch (Exception e){
                log.error("线程：{}充值失败：{}",Thread.currentThread().getName(),e.getMessage());
            }
        }
    }

    private void doLess10000Pay(Long uid, String batchPayId) {
        BigDecimal amount = BigDecimal.valueOf(5000);
        PayVo payVo = PayVo.builder()
                .uid(uid)
                .amount(amount)
                .build();
        // todo: 优化if嵌套结构
        while (true){
            try{
                String transactionId = UUID.randomUUID().toString();
                payVo.setTransactionId(transactionId);
                String requestID = UUID.randomUUID().toString();
                ResponseEntity<String> response = feinClient.getMoneyFromUsersAccount(
                        AuthConstant.STUDENT_ID,
                        requestID,
                        payVo
                );
                Assert.isTrue(response.getStatusCode().is2xxSuccessful(), "Request was not successful");
//                log.info("线程：{}请求成功", Thread.currentThread().getName());
                JSONObject body = JSON.parseObject(response.getBody());
                if (body != null) {
                    Integer code = (Integer) body.get("code");
                    if(code != null && code == 501){
                        if(amount.compareTo(new BigDecimal("0.01")) <= 0){
                            log.info("账户：{}扣款完成", uid);
                            // 批处理任务计数减一
                            redisTemplate.opsForValue().decrement("batchPay:"+ batchPayId);
                            String freeTask = redisTemplate.opsForValue().get("batchPay:" + batchPayId);
                            if(freeTask == null || Long.parseLong(freeTask) <= 0){
                                log.info("充值批处理完成");
                                // 删除任务计数
                                redisTemplate.delete("batchPay:"+ batchPayId);
                                // todo:回调函数
                                String requestId = UUID.randomUUID().toString();
                                ResponseEntity<String> paySureResponse = feinClient.ensurePaySuccess(AuthConstant.STUDENT_ID
                                        , requestId, batchPayId);
                                log.info("批处理充值回调：{}", paySureResponse.getBody());
                            }
                            return;
                        }
                        // 充值失败
//                        log.warn("充值{}失败",amount);
                        amount = amount.divide(new BigDecimal("2"), 2, RoundingMode.HALF_UP);
                        payVo.setAmount(amount);
                        continue;
                    }else if(code != null && code == 404){
                        log.error("资金账户：{}不存在", payVo.getUid());
                        redisTemplate.opsForValue().decrement("batchPay:"+ batchPayId);
                        int count = Integer.parseInt(Objects.requireNonNull(redisTemplate.opsForValue().get("batchPay:" + batchPayId)));
                        if(count <= 0){
                            redisTemplate.delete("batchPay:"+ batchPayId);
                        }
                        return;
                    }
                    String addAmount = amount.setScale(2, RoundingMode.HALF_UP).toString();
//                    stringRedisTemplate.opsForHash().increment("user", payVo.getUid().toString(), addAmount);
                    payToRedis(uid, addAmount);
//                    log.info("充值{}成功",amount);
                }else {
                    log.error("未知错误");
                }
            }catch (Exception exception){
                log.error("线程 {} 充值失败准备重试: {}" , Thread.currentThread().getName(), exception.getMessage());
            }
        }
    }

}
